
#include <string>
#include <iostream>
#include <png.h>

#include "texture.h"

using namespace std;

unsigned char *load_png(const char *name, int &width, int &height) {
   
    FILE *file = fopen(name,"rb");
    if(!file)
    {
        fprintf(stderr,"Texture::load_png(): error open \"%s\" file\n",name);
        return NULL;
    }
    png_byte sig[8];
    fread(sig,8,1,file);
    if(!png_check_sig(sig,8))
    {
        fprintf(stderr,"Texture::load_png(): wrong signature in \"%s\" file\n",name);
        fclose(file);
        return NULL;
    }
    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,0,0,0);
    if(!png_ptr)
    {
        fclose(file);
        return NULL;
    }
    png_infop info_ptr = png_create_info_struct(png_ptr);
    if(!info_ptr)
    {
        png_destroy_read_struct(&png_ptr,0,0);
        fclose(file);
        return NULL;
    }
    png_init_io(png_ptr,file);
    png_set_sig_bytes(png_ptr,8);
    png_read_info(png_ptr,info_ptr);
    unsigned long w,h;
    int bit_depth,color_type;
    png_get_IHDR(png_ptr,info_ptr,&w,&h,&bit_depth,&color_type,0,0,0);
    if(bit_depth == 16) png_set_strip_16(png_ptr);
    if(color_type == PNG_COLOR_TYPE_PALETTE) png_set_expand(png_ptr);
    if(bit_depth < 8) png_set_expand(png_ptr);
    if(png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS)) png_set_expand(png_ptr);
    if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png_ptr);
    double gamma;
    if(png_get_gAMA(png_ptr,info_ptr,&gamma)) png_set_gamma(png_ptr,(double)2.2,gamma);
    png_read_update_info(png_ptr,info_ptr);
    png_get_IHDR(png_ptr,info_ptr,&w,&h,&bit_depth,&color_type,0,0,0);
    png_uint_32 row_bytes = png_get_rowbytes(png_ptr,info_ptr);
    png_uint_32 channels = png_get_channels(png_ptr,info_ptr);
    png_byte *img = new png_byte[row_bytes * h];
    png_byte **row = new png_byte*[h];
    for(int i = 0; i < (int)h; i++)
    {
        row[i] = img + row_bytes * i;
    }
    png_read_image(png_ptr,row);
    png_read_end(png_ptr,NULL);
    png_destroy_read_struct(&png_ptr,0,0);
    fclose(file);
    delete [] row;
    width = w;
    height = h;
    unsigned char *data = new unsigned char[width * height * 4];
    unsigned char *ptr = data;
    for(int i = 0; i < height; i++)
    {
        for(int j = 0; j < width; j++)
        {
            int k = row_bytes * i + j * channels;
            *ptr++ = img[k + 0];
            *ptr++ = img[k + 1];
            *ptr++ = img[k + 2];
            if(channels == 4) *ptr++ = img[k + 3];
            else *ptr++ = 255;
        }
    }
    delete [] img;
    return data;
}

unsigned char *rgba2rgb(unsigned char *data,int width,int height)
{
    unsigned char *dest = new unsigned char[width * height * 3];
    unsigned char *d = dest;
    unsigned char *s = data;
    for(int i = 0; i < width * height; i++)
    {
        *d++ = *s++;
        *d++ = *s++;
        *d++ = *s++;
        s++;
    }
    delete [] data;
    return dest;
}

unsigned char *rgba2luminance(unsigned char *data,int width,int height)
{
    unsigned char *dest = new unsigned char[width * height];
    unsigned char *d = dest;
    unsigned char *s = data;
    for(int i = 0; i < width * height; i++)
    {
        *d++ = *s++;
        s += 3;
    }
    delete [] data;
    return dest;
}

unsigned char *rgba2luminance_alpha(unsigned char *data,int width,int height)
{
    unsigned char *dest = new unsigned char[width * height * 2];
    unsigned char *d = dest;
    unsigned char *s = data;
    for(int i = 0; i < width * height; i++)
    {
        *d++ = *s++;
        s += 2;
        *d++ = *s++;
    }
    delete [] data;
    return dest;
}

unsigned char *load(const char *name, int &width, int &height) {
   
   if(strstr(name,".png")) return load_png(name, width, height);
   fprintf(stderr,"Texture::load(): unknown format of \"%s\" file\n",name);
   
   return 0;
}

/// 

Texture::Texture() {
   
   width = 1; height = 1;
   target = GL_TEXTURE_2D;
   type = GL_UNSIGNED_BYTE;
   format = GL_RGBA;
   internalformat = format;
}

Texture::Texture(const char *filename, GLuint t) {
   
   width = 1; height = 1;
   target = t;
   type = GL_UNSIGNED_BYTE;
   format = GL_RGBA;
   internalformat = format;
   
   if (not load(filename))  { cout<<"no texture loaded "<<endl; exit(0); }
}

Texture::~Texture() {
   
}

bool Texture::load(const char *filename) {
   
   //~ glActiveTexture(GL_TEXTURE2);
   glGenTextures(1,&id);
   glBindTexture(target,id);
   glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT);
   glTexParameteri (target, GL_TEXTURE_WRAP_T, GL_REPEAT);
   glTexParameteri (target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glTexParameteri (target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   
   if (target == GL_TEXTURE_2D || target == GL_TEXTURE_RECTANGLE_NV) {
      unsigned char *data = ::load(filename,width,height);
      if (data == 0) return false;
      if (format != GL_RGBA) {
         if (format == GL_LUMINANCE) data = rgba2luminance(data,width,height);
         else if(format == GL_LUMINANCE_ALPHA) data = rgba2luminance_alpha(data,width,height);
         else if(format == GL_RGB) data = rgba2rgb(data,width,height);
      }
      glTexImage2D(target,0,internalformat,width,height,0,format,GL_UNSIGNED_BYTE,data);
      if(data) delete []data;
      return true;
   }
   
   return false;
}

void Texture::bind() {
   
   glBindTexture(target,id);
}
